package myzap

import (
	"github.com/gin-gonic/gin"
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"net"
	"net/http"
	"net/http/httputil"
	"runtime/debug"
	"strings"

	"os"
	"path"
	"time"
)

var (
	consoleConfig ConsoleConfig
	fileConfig    FileConfig
	Logger        *zap.SugaredLogger
)

// LogConfig 日志接口
type LogConfig interface {
	SetLevel(level string) bool
	GetLevel() string
}

// ConsoleConfig 终端日志配置
type ConsoleConfig struct {
	atomicLevel zap.AtomicLevel
	encoder     zapcore.Encoder
}

func (c *ConsoleConfig) SetLevel(level string) bool {
	parseLevel, err := zapcore.ParseLevel(level)
	if err != nil {
		Logger.Error("parse level error", zap.Error(err))
		return false
	}
	c.atomicLevel.SetLevel(parseLevel)
	return true
}

func (c *ConsoleConfig) GetLevel() string {
	return c.atomicLevel.String()
}

func (c *ConsoleConfig) Init(level string) {
	c.atomicLevel, _ = zap.ParseAtomicLevel(level)
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
	c.encoder = zapcore.NewConsoleEncoder(encoderConfig)
}

type FileConfig struct {
	atomicLevel zap.AtomicLevel
	encoder     zapcore.Encoder
	name        string
	maxSize     int
	maxBackups  int
	maxAge      int
	compress    bool
}

func (c *FileConfig) SetLevel(level string) bool {
	parseLevel, err := zapcore.ParseLevel(level)
	if err != nil {
		Logger.Error("parse level error", zap.Error(err))
		return false
	}
	c.atomicLevel.SetLevel(parseLevel)
	return true
}

func (c *FileConfig) GetLevel() string {
	return c.atomicLevel.String()
}

func (c *FileConfig) Init(name string, maxSize, maxBackup, maxAge int, compress bool, level string) {
	c.name = name
	c.maxSize = maxSize
	c.maxBackups = maxBackup
	c.maxAge = maxAge
	c.compress = compress
	c.atomicLevel, _ = zap.ParseAtomicLevel(level)
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	c.encoder = zapcore.NewConsoleEncoder(encoderConfig)
}

func ensureDir(filePath string) error {
	dir := path.Dir(filePath)
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		return os.MkdirAll(dir, 0755)
	}
	return nil
}

func InitLogger(filename string, fileLevel string, maxSize, maxBackups, maxAge int, compress bool,
	consoleLevel string) {
	// 确保目录存在且具有正确的权限
	if err := ensureDir(filename); err != nil {
		Logger.Errorf("log create failed, err: %v", err)
		return
	}

	consoleConfig.Init(consoleLevel)
	fileConfig.Init(filename, maxSize, maxBackups, maxAge, compress, fileLevel)

	// 创建分级写入器（Tee），日志将同时写入文件和控制台
	core := zapcore.NewTee(
		zapcore.NewCore(consoleConfig.encoder, zapcore.AddSync(os.Stdout), consoleConfig.atomicLevel),
		zapcore.NewCore(fileConfig.encoder, zapcore.AddSync(&lumberjack.Logger{
			Filename:   filename,   //文件路径
			MaxSize:    maxSize,    //日志文件的最大存储量（单位MB），否则切割
			MaxBackups: maxBackups, //最多保留的文件数量
			MaxAge:     maxAge,     //旧文件最多保存的天数
			Compress:   compress,   //是否压缩
		}), fileConfig.atomicLevel),
	)

	Logger = zap.New(core, zap.AddCaller()).Sugar()
}

func GetConsoleLevel() string {
	return consoleConfig.GetLevel()
}

func GetFileLevel() string {
	return fileConfig.GetLevel()
}

func SetConsoleLevel(level string) {
	consoleConfig.SetLevel(level)
}

func SetFileLevel(level string) {
	fileConfig.SetLevel(level)
}

// GinLogger 接收gin框架默认的日志
func GinLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery
		c.Next()

		cost := time.Since(start)
		Logger.Info(path,
			zap.Int("status", c.Writer.Status()),
			zap.String("method", c.Request.Method),
			zap.String("path", path),
			zap.String("query", query),
			zap.String("ip", c.ClientIP()),
			zap.String("user-agent", c.Request.UserAgent()),
			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
			zap.Duration("cost", cost),
		)
	}
}

// GinRecovery recover掉项目可能出现的panic，并使用zap记录相关日志
func GinRecovery(stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				// Check for a broken connection, as it is not really a
				// condition that warrants a panic stack trace.
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					Logger.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}

				if stack {
					Logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					Logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}
